﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using MyBasicLib.Extensions;
using MyBasicLib.Linq.Expressions;
using MyBasicLib.Reflection;
using MyBasicLibLinqExpressions = MyBasicLib.Linq.Expressions;
namespace MyBasicLib.Linq.Expressions
{
    
    /// <summary>
    /// Performs bottom-up analysis to determine which nodes can possibly
    /// be part of an evaluated sub-tree.
    /// </summary>
    public class Nominator : System.Linq.Expressions.ExpressionVisitor
    {
        private readonly Func<Expression, bool> m_fnCanBeEvaluated;
        private HashSet<Expression> m_candidates;
        private bool m_cannotBeEvaluated;

        public Nominator()
            : this(CanBeEvaluatedLocally)
        {
        }

        public Nominator(Func<Expression, bool> fnCanBeEvaluated)
        {
            m_fnCanBeEvaluated = fnCanBeEvaluated ?? CanBeEvaluatedLocally;
        }

        private static bool CanBeEvaluatedLocally(Expression exp)
        {
            return exp.NodeType != ExpressionType.Parameter &&
                   exp.NodeType != ExpressionType.MemberInit && exp.NodeType != ExpressionType.New;
        }

        internal HashSet<Expression> Nominate(Expression expression)
        {
            m_candidates = new HashSet<Expression>();
            Visit(expression);
            return m_candidates;
        }

        public override Expression Visit(Expression expression)
        {
            if (expression != null)
            {
                bool saveCannotBeEvaluated = m_cannotBeEvaluated;
                m_cannotBeEvaluated = false;

                base.Visit(expression);

                if (!m_cannotBeEvaluated)
                {
                    if (m_fnCanBeEvaluated(expression))
                    {
                        if (expression.NodeType != ExpressionType.Constant)
                            m_candidates.Add(expression);
                    }
                    else
                    {
                        m_cannotBeEvaluated = true;
                    }
                }

                m_cannotBeEvaluated |= saveCannotBeEvaluated;
            }

            return expression;
        }
    }
}